package physics.globalforce;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import physics.Assembly;
import physics.EnvironmentSingleton.EnvironmentalForce;
import physics.Simulation;
import physics.component.Mass;


/**
 * An individual wall force. The behavior is dependent on which wall "side" the
 * Wall is instantiated with.
 *
 * @author Kevin Gao, Connor Gordon
 *
 */
public class Wall extends ExponentialForce {

    /**
     * Constant identifying top wall.
     */
    public static final int TOP_WALL = 1;
    /**
     * Constant identifying right wall.
     */
    public static final int RIGHT_WALL = 2;
    /**
     * Constant identifying bottom wall.
     */
    public static final int BOTTOM_WALL = 3;
    /**
     * Constant identifying left wall.
     */
    public static final int LEFT_WALL = 4;
    /**
     * Constant that defines increments that wall should increase and decrease
     * in.
     */
    private static final int OFFSET_DELTA = 10;

    /**
     * Offset from actual wall.
     */
    private static int ourOffset = 0;

    /**
     * Bit mask used to keep track of which walls are enabled.
     */
    private static int ourEnabledWallBitMask = Integer.parseInt("1111", 2);

    private final int mySide;
    private final Simulation mySimulation;

    /**
     * Constructor for new wall repulsion force.
     *
     * @param side Which side the wall represents
     * @param magnitude Magnitude of repulsion force
     * @param exponent Inverse exponent to scale distance by
     * @param simulation Simulation that contains the force
     */
    public Wall (final int side, final double magnitude, final double exponent,
            final Simulation simulation) {
        super(EnvironmentalForce.WALL, magnitude, exponent);
        mySide = side;
        mySimulation = simulation;
    }

    @Override
    protected final double getDistance (final Mass target, final Assembly asm) {
        Point origin = null;
        Point2D targetCenter = target.getCenter();
        Dimension canvasSize = mySimulation.getSize();
        switch (mySide) {
            case TOP_WALL:
                origin = new Point((int) targetCenter.getX(), ourOffset);
                break;
            case RIGHT_WALL:
                origin = new Point((int) targetCenter.getX(), canvasSize.width
                        - ourOffset);
                break;
            case BOTTOM_WALL:
                origin = new Point((int) targetCenter.getX(), canvasSize.height
                        - ourOffset);
                break;
            case LEFT_WALL:
                origin = new Point(ourOffset, (int) targetCenter.getY());
                break;
            default:
                origin = new Point(0, 0);
                break;
        }
        return origin.distance(targetCenter);
    }

    @Override
    protected final double getAngle (final Mass target, final Assembly asm) {
        switch (mySide) {
            case TOP_WALL:
                return Mass.DOWN;
            case RIGHT_WALL:
                return Mass.LEFT;
            case BOTTOM_WALL:
                return Mass.UP;
            case LEFT_WALL:
                return Mass.RIGHT;
            default:
                return 0;
        }
    }

    /**
     * Increments the offset (moves inward) the walls by a constant delta.
     */
    public static final void decreaseArea () {
        ourOffset += OFFSET_DELTA;
    }

    /**
     * Decrements the offset (moves inward) the walls by a constant delta.
     */
    public static final void increaseArea () {
        ourOffset -= OFFSET_DELTA;
    }

    /**
     * Getter for the current wall offset.
     *
     * @return offset for walls
     */
    public static final int getOffset () {
        return ourOffset;
    }

    /**
     * Enable a wall.
     *
     * @param side side to enable
     */
    public static final void enableWall (final int side) {
        // Set the (size-1) lowest bit to 1
        ourEnabledWallBitMask |= 0x1 << (side - 1);
    }

    /**
     * Disable a wall.
     *
     * @param side side to disable
     */
    public static final void disableWall (final int side) {
        // Set the (size-1) lowest bit to 0
        ourEnabledWallBitMask &= ~(0x1 << (side - 1));
    }

    /**
     * Toggles a wall.
     *
     * @param side side to toggle
     */
    public static final void toggleWall (final int side) {
        if (isWallEnabled(side)) {
            disableWall(side);
        }
        else {
            enableWall(side);
        }
    }

    /**
     * Checks if side is enabled.
     *
     * @param side side to check
     * @return true if side is enabled; false if side is not enabled
     */
    public static final boolean isWallEnabled (final int side) {
        // Check if bit is flipped on. e.g. for top wall
        // 0x1111 & (0x1 << 0) = 0x1, which is > 0
        // 0x1110 & (0x1 << 0) = 0x0, which is = 0
        return (ourEnabledWallBitMask & (0x1 << (side - 1))) > 0;
    }

    @Override
    public final boolean isEnabled () {
        return isWallEnabled(mySide);
    }

}
